package com.project.common.core.utils.weChat;

import com.google.common.collect.Maps;
import com.project.common.core.config.prop.PropAttributes;
import com.project.common.core.config.prop.PropConfig;
import com.project.common.core.utils.DateUtil;
import com.project.common.core.utils.HttpUtil;
import com.project.common.core.utils.JacksonUtil;
import com.project.common.core.utils.SpringUtils;
import com.project.common.core.utils.exception.BASE_RESP_CODE_ENUM;
import com.project.common.core.utils.exception.BaseCustomException;
import com.project.common.core.utils.exception.CustomException;
import com.project.common.core.utils.id.IDUtil;
import com.project.common.core.utils.redis.RedisClient;
import com.project.common.core.utils.redis.RedisConsts;
import com.project.common.core.utils.wx.pojo.AccessToken;
import com.project.common.core.utils.wx.pojo.WeChat;
import net.sf.json.JSONObject;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 微信小程序工具类
 *
 * @author wyy
 * @date 2019-08-21 14:38
 */
@Component
public class WeChatMiniUtil {
    /**
     * 日志对象
     */
    private static final Logger log = LoggerFactory.getLogger(WeChatMiniUtil.class);

    /**
     * Redis客户端
     */
    private static RedisClient REDIS_CLIENT = null;


    /**
     * 微信小程序授权【auth.code2Session】
     */
    public static final String CODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session";

    /**
     * 获取token的url
     */
    public static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";

    /**
     * 创建二维码url
     */
    public static final String UNLIMIT_QR_CODE_URL = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=ACCESS_TOKEN";

    /**
     * 敏感内容校验
     */
    public static final String MSG_SEC_CHECK_URL = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN";

    /**
     * 客服接口-发消息
     */
    public static final String WX_CUSTOM_SEND_MESSAGE = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN";

    // 临时素材上传
    public static String UPLOAD_TEMP_MEDIAPATH = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESSTOKEN&type=UPTYPE";

    /**
     * 获取单例RedisClient客户端单例对象
     *
     * @return
     */
    private static RedisClient getRedisClient() {
        if (REDIS_CLIENT == null) {
            synchronized (WeChatMiniUtil.class) {
                if (REDIS_CLIENT == null) {
                    REDIS_CLIENT = SpringUtils.getBean("redisClient", RedisClient.class);
                }
            }
        }
        return REDIS_CLIENT;
    }

    /**
     * 获取调用微信小程序接口AccessToken
     *
     * @return 微信token
     */
    public static String getAccessToken() {
        return getAccessToken(1);
    }

    /**
     * 获取调用微信小程序接口AccessToken
     *
     * @param tryCount 尝试调用次数【最大三次】
     * @return
     */
    public static String getAccessToken(int tryCount) {
        // 获取AccessToken
        String accessToken = (String) getRedisClient().get(RedisConsts.WX_MINI_ACCESS_TOKEN);
        try {
            if (StringUtils.isBlank(accessToken)) {
                log.info("\r\n ********* 小程序redisToken过期");
                accessToken = getAccessTokenByApi();
                if (accessToken == null && tryCount <= 3) {
                    return getAccessToken(tryCount++);
                } else if (accessToken == null && tryCount > 3) {
                    //重试3次后仍未获取到锁，直接返回null
                    return null;
                }
            }
        } catch (Exception e) {
            log.error("\r\n ***********获取accessToken出错：{}", ExceptionUtils.getFullStackTrace(e));
        }
        log.info("\r\n ********* redis获取小程序Token--{}", com.alibaba.fastjson.JSONObject.toJSONString(accessToken));
        return StringUtils.isBlank(accessToken) ? null : accessToken;
    }

    /**
     * 通过微信接口获取access_token
     *
     * @return
     */
    private static String getAccessTokenByApi() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("grant_type", "client_credential");
        map.put("appid", PropConfig.getProperty(PropAttributes.THIRDPARTY_WX_MINI_APPID));
        map.put("secret", PropConfig.getProperty(PropAttributes.THIRDPARTY_WX_MINI_APPSECRET));

        //GET方式获取Accesstoken
        JSONObject accessTokenObj = HttpUtil.getJSONObjectFromHttpsGet(ACCESS_TOKEN_URL, map);
        if (accessTokenObj != null && accessTokenObj.containsKey("access_token")) {
            return initAccessToken(accessTokenObj);
        } else {
            //POST方式获取Accesstoken
            accessTokenObj = HttpUtil.getJSONObjectFromHttpsPost(ACCESS_TOKEN_URL, map);
            if (accessTokenObj != null && accessTokenObj.containsKey("access_token")) {
                return initAccessToken(accessTokenObj);
            }
        }
        return null;
    }

    /**
     * 构建AccessToken，并且放入redis缓存
     *
     * @param accessTokenObj accessToken接口返回数据
     * @return
     */
    private static String initAccessToken(JSONObject accessTokenObj) {
        //构建AccessToken
        AccessToken accessToken = new AccessToken(accessTokenObj.getString("access_token"), accessTokenObj.getInt("expires_in"));

        //将AccessToken放入redis缓存
        String token = accessToken.getToken();
        getRedisClient().set(RedisConsts.WX_MINI_ACCESS_TOKEN, token, accessToken.getExpireIn() / 2);
        return token;
    }

    /**
     * 根据小程序授权CODE换取OpenID
     *
     * @param jsCode 小程序登录时获取的code
     * @return JSONObject
     */
    public static JSONObject jsCode2session(String jsCode) {
        // 初始化接口请求参数
        Map<String, Object> paramMap = new LinkedHashMap<String, Object>();
        paramMap.put("appid", PropConfig.getProperty(PropAttributes.THIRDPARTY_WX_MINI_APPID));
        paramMap.put("secret", PropConfig.getProperty(PropAttributes.THIRDPARTY_WX_MINI_APPSECRET));
        paramMap.put("js_code", jsCode);
        paramMap.put("grant_type", "authorization_code");

        // 调用换取API
        JSONObject obj = HttpUtil.getJSONObjectFromHttpsGet(CODE_2_SESSION_URL, paramMap);
        log.info("【jscode2session】微信返回的信息：{}", String.valueOf(obj));
        if (obj == null || obj.containsKey("errcode")) {
            log.error("调用【getCode2Session】出错：{}", obj != null ? String.valueOf(obj) : "");
            throw new BaseCustomException("根据小程序授权CODE换取OpenID!", String.valueOf(obj));
        }
        return obj;
    }

    /**
     * 生成永久有效无限制的小程序码
     *
     * @param sceneStr  场景值
     * @param pageUri   小程序路由页面URI【不填默认主页】
     * @param width     二维码大小 【最小 280px，最大 1280px】
     * @param lineCode  线条颜色RGB【{"r":0,"g":0,"b":0}】
     * @param isHyaLine 透明底色
     * @return
     */
    public static String getMiniQrCodeImg(String sceneStr, String pageUri, int width, Map<String, Object> lineCode, boolean isHyaLine) {
        log.debug("\r\n ************************ 保存二维码buffer START ************************* \r\n");
        // 获取二维码buffer
        byte[] qrCodeByte = getUnlimitedMiniCode(sceneStr, pageUri, width, lineCode, isHyaLine);
        log.debug("\r\n ************************ 二维码图片byte[]:{} ************************* \r\n");
        log.debug("{}", qrCodeByte);
        // 创建二维码空白文件
        String filePath = PropConfig.getProperty(PropAttributes.NFS__SERVICE_FILE_SHARE_PATH) + "/" + "qrCode";
        String fileName = IDUtil.getId() + ".jpeg";
        log.info("小程序预览码图片{}:{}", filePath, fileName);
        File tempFile = new File(filePath, fileName);
        String qrCodeUrl = null;
        if (!tempFile.exists()) {
            try {
                tempFile.getParentFile().mkdirs();
                tempFile.createNewFile();

                log.info("小程序预览码图片writeByteArrayToFile ,{} ,{}", tempFile.getName(), qrCodeByte.length);
                FileUtils.writeByteArrayToFile(tempFile, qrCodeByte);

//                qrCodeUrl = OssUtils.upLoad(new FileInputStream(tempFile), FilePathConsts.SECOND_ENUM.COURSE_INFO.getFileCode(), fileName);
            } catch (IOException e) {
                log.debug("创建文件失败{}", ExceptionUtils.getFullStackTrace(e));
                e.printStackTrace();
            }
        }
        log.debug("\r\n ************************ 保存二维码buffer END ************************* \r\n");

        // 封装返回图片地址路径
        return qrCodeUrl;
    }


    /**
     * 生成二维码
     *
     * @param sceneStr  场景值
     * @param pageUri   小程序路由页面URI【不填默认主页】
     * @param width     二维码大小 【最小 280px，最大 1280px】
     * @param lineCode  线条颜色RGB【{"r":0,"g":0,"b":0}】
     * @param isHyaLine 透明底色
     * @return
     */
    public static String getMiniQRCodeImg(String sceneStr, String pageUri, int width, Map<String, Object> lineCode, boolean isHyaLine) {
        log.debug("\r\n ************************ 保存二维码buffer START ************************* \r\n");
        // 获取二维码buffer
        byte[] qrCodeByte = getUnlimitedMiniCode(sceneStr, pageUri, width, lineCode, isHyaLine);
        log.debug("\r\n ************************ 二维码图片byte[]:{} ************************* \r\n");
        log.debug("{}", qrCodeByte);
        // 创建二维码空白文件
        String filePath = PropConfig.getProperty(PropAttributes.NFS__SERVICE_FILE_TEMP_PATH) + "/" + "qrCode";
        String fileName = IDUtil.getId() + ".jpeg";
        log.info("小程序预览码图片{}:{}", filePath, fileName);
        File tempFile = new File(filePath, fileName);
        if (!tempFile.exists()) {
            try {
                tempFile.getParentFile().mkdirs();
                tempFile.createNewFile();

                log.info("小程序预览码图片writeByteArrayToFile ,{} ,{}", tempFile.getName(), qrCodeByte.length);
                FileUtils.writeByteArrayToFile(tempFile, qrCodeByte);

            } catch (IOException e) {
                log.debug("创建文件失败{}", ExceptionUtils.getFullStackTrace(e));
                e.printStackTrace();
            }
        }
        log.debug("\r\n ************************ 保存二维码buffer END ************************* \r\n");

        // 封装返回图片地址路径
        return filePath + "/" + fileName;
    }


    /**
     * 生成永久有效无限制的小程序码
     *
     * @param sceneStr  场景值
     * @param pageUri   小程序路由页面URI【不填默认主页】
     * @param width     二维码大小 【最小 280px，最大 1280px】
     * @param lineCode  线条颜色RGB【{"r":0,"g":0,"b":0}】
     * @param isHyaLine 透明底色
     * @return
     */
    public static byte[] getUnlimitedMiniCode(String sceneStr, String pageUri, int width, Map<String, Object> lineCode, boolean isHyaLine) {
        log.info("\r\n ******************** 开始调用二维码URL *********************");
        byte[] result = null;
        try {
            // 初始化请求参数集合
            Map<String, Object> paramMap = new LinkedHashMap();
            String accessToken = getAccessToken(1);
            log.info("\r\n ******************** accessToken:{} *********************", accessToken);
            paramMap.put("scene", URLDecoder.decode(sceneStr, "utf-8"));
            if (StringUtils.isNotBlank(pageUri)) {
                paramMap.put("page", pageUri);
            }
            paramMap.put("width", width);
            paramMap.put("auto_color", false);
            if (MapUtils.isNotEmpty(lineCode)) {
                paramMap.put("line_color", lineCode);
            }
            paramMap.put("is_hyaline", isHyaLine);

            // 请求生成二维码
            result = HttpUtil.httpsPost(UNLIMIT_QR_CODE_URL.replace("ACCESS_TOKEN", accessToken), "UTF-8", paramMap);
        } catch (Exception e) {
            log.info("\r\n ***********生成永久有效无限制的小程序码报错:{}", ExceptionUtils.getStackTrace(e));
        }
        return result;
    }

    /**
     * 敏感内容校验
     *
     * @param comment 评论内容
     * @return
     */
    public static boolean getCommentCheck(String comment) {
        log.info("\r\n *****************开始校验敏感内容信息*****************");
        try {
            String accessToken = getAccessToken(1);

            Map<String, Object> paramMap = Maps.newHashMap();
            paramMap.put("content", comment);

            String body = JSONObject.fromObject(paramMap).toString();
            String result = HttpUtil.httpsPost(MSG_SEC_CHECK_URL.replaceAll("ACCESS_TOKEN", accessToken), body, "utf-8");
            JSONObject resultJson = JSONObject.fromObject(result);

            if (0 == (int) resultJson.get("errcode") && "ok".equals(resultJson.get("errmsg"))) {
                return true;
            } else {
                throw new CustomException(resultJson.get("errcode").toString(), resultJson.get("errmsg").toString());
            }
        } catch (Exception e) {
            log.info("\r\n ***********小程序敏感内容报错*****:{}", ExceptionUtils.getStackTrace(e));
        }
        return false;
    }

    /**
     * 客服接口-发消息
     *
     * @param message 需要发送的消息
     * @param openId  接收人的openId
     */
    public static void sendCustomMessage(String message, String openId) {
        // 初始化客服接口-发消息信息
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        map.put("touser", openId);
        map.put("msgtype", "text");
        // 发送的消息
        Map<String, Object> messageMap = new LinkedHashMap<>();
        messageMap.put("content", message);
        map.put("text", messageMap);

        // 将map转成String
        String result = null;
        try {
            result = JacksonUtil.toJsonStr(map);
        } catch (IOException e) {
            log.info("\r\n ***********客服接口发消息失败:{}", ExceptionUtils.getStackTrace(e));
            throw new BaseCustomException(BASE_RESP_CODE_ENUM.SERVER_ERROR.getCode(), "客服接口发消息失败");
        }

        // 发送消息
        log.info("发送客服消息：{}", result);
        HttpUtil.httpsPost(WX_CUSTOM_SEND_MESSAGE.replace("ACCESS_TOKEN", getAccessToken()), result);
    }

    /**
     * 客服接口-发消息
     *
     * @param mediaId 媒体ID
     * @param openId  接收人的openId
     */
    public static void sendCustomMessageOfMedia(String mediaId, String openId) {
        // 初始化客服接口-发消息信息
        Map<String, Object> map = new LinkedHashMap<String, Object>();
        map.put("touser", openId);
        map.put("msgtype", "image");
        // 发送的消息
        Map<String, Object> messageMap = new LinkedHashMap<>();
        messageMap.put("media_id", mediaId);
        map.put("image", messageMap);

        // 将map转成String
        String result = "";
        try {
            result = JacksonUtil.toJsonStr(map);
        } catch (IOException e) {
            log.info("\r\n ***********客服接口发消息失败:{}", ExceptionUtils.getStackTrace(e));
            throw new BaseCustomException(BASE_RESP_CODE_ENUM.SERVER_ERROR.getCode(), "客服接口发消息失败");
        }

        // 发送消息
        log.info("发送客服消息：{}", result);
        HttpUtil.httpsPost(WX_CUSTOM_SEND_MESSAGE.replace("ACCESS_TOKEN", getAccessToken()), result);
    }

    /**
     * 验证微信签名【验证服务器地址】
     *
     * @param wechat 验证对象
     * @return
     */
    public static boolean checkSign(WeChat wechat) {
        try {
//            return WeChatSignUtil.checkSign(PropConfig.getProperty(PropAttributes.THIRDPARTY_WX_MINI_TOKEN), wechat);
            return true;
        } catch (Exception e) {
            log.error("验证微信签名出错：{}", ExceptionUtils.getFullStackTrace(e));
            return false;
        }

    }

    /**
     * 上传临时素材
     *
     * @param imgUrl 图片公网地址
     * @return
     */
    public static String uploadTempMedia(String imgUrl) {
        try {
            // 下载公网图片到服务器
            byte[] bytes = IOUtils.toByteArray(new URL(imgUrl));
            String materialTempPath = PropConfig.getProperty(PropAttributes.NFS__SERVICE_FILE_TEMP_PATH) + File.separator + DateUtil.convertDateToStr(new Date(), "yyyy-MM-dd") + File.separator + System.currentTimeMillis() + RandomStringUtils.randomNumeric(6) + ".png";
            FileUtils.writeByteArrayToFile(new File(materialTempPath), bytes);

            // 获取接口令牌
            String accessToken = getAccessToken();
            String url = UPLOAD_TEMP_MEDIAPATH.replace("ACCESSTOKEN", accessToken);
            url = url.replace("UPTYPE", "image");
            String result = HttpUtil.uploadFile(url, materialTempPath);
            log.info("*********上传临时素材返回结果:{}", result);
            JSONObject jsonObject = JSONObject.fromObject(result);
            String mediaId = jsonObject.getString("media_id");
            if (StringUtils.isNotBlank(mediaId)) {
                return mediaId;
            }
        } catch (IOException e) {
            log.error("临时媒体上传出错:{}", ExceptionUtils.getFullStackTrace(e));
            e.printStackTrace();
        }
        return "";
    }

}
